﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using WcfContrib.Security;
using WcfContrib.Errors;
using WcfContrib.Handlers;

namespace WcfContrib.Extensions.Context
{
    /// <summary>
    /// Represents an extension for a service activation.
    /// You can get the attached extension through the Current static property.
    /// Gives you access to activation related information and settings.
    /// </summary>
    public sealed class ActivationContextExtension : IExtension<OperationContext>, IDisposable
    {
        #region Fields
        MessageBuffer _messageBuffer = null;
        Message _requestMessage;
        bool _errorContextLoaded;
        bool _createBufferForErrors;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the action.
        /// </summary>
        /// <value>The action.</value>
        public string Action { get; internal set; }
        /// <summary>
        /// Gets the name of the identity assoicated with the claim identity source.
        /// </summary>
        /// <value>The name of the identity.</value>
        public string IdentityName { get; internal set; }
        /// <summary>
        /// Gets the operation parameter values.
        /// </summary>
        /// <value>The parameters.</value>
        public object[] Parameters { get; private set; }
        /// <summary>
        /// Gets the to message header value
        /// </summary>
        public string ToHeader { get; private set; }

        /// <summary>
        /// Gets the type of the service.
        /// </summary>
        /// <value>The type of the service.</value>
        public Type ServiceType { get; internal set; }
        /// <summary>
        /// Gets the name of the service.
        /// </summary>
        /// <value>The name of the service.</value>
        public string ServiceName { get; internal set; }
        /// <summary>
        /// Gets the service namespace.
        /// </summary>
        /// <value>The service namespace.</value>
        public string ServiceNamespace { get; internal set; }

        /// <summary>
        /// Gets the call items.
        /// </summary>
        /// <value>The items.</value>
        public Dictionary<string, object> CallItems { get; private set; }
        /// <summary>
        /// Gets the session items.
        /// </summary>
        /// <value>The session items.</value>
        public Dictionary<string, object> SessionItems { get; private set; }

        ActivationErrorContext ErrorContext { get; set; }
        #endregion

        #region Instancing
        /// <summary>
        /// Gets the activaction extension attached on the current operation context.
        /// </summary>
        /// <value>The current.</value>
        public static ActivationContextExtension Current
        {
            get
            {
                if (OperationContext.Current != null)
                {
                    return OperationContext.Current.Extensions.Find<ActivationContextExtension>();
                }

                return null;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ActivationContextExtension"/> class.
        /// </summary>
        /// <param name="requestMessage">The request message.</param>
        public ActivationContextExtension(Message requestMessage)
        {
            CallItems = new Dictionary<string, object>();
            SessionItems = (SessionContextExtension.Current != null) ? SessionContextExtension.Current.Items : CallItems;

            _requestMessage = requestMessage;

            if (requestMessage != null && requestMessage.Headers != null)
            {
                Action = requestMessage.Headers.Action;

                if (requestMessage.Headers.To != null)
                {
                    ToHeader = requestMessage.Headers.To.AbsoluteUri;
                }
            }

            if (OperationContext.Current != null)
            {
                ServiceSecurityContext sec = null;
                try
                {
                    sec = OperationContext.Current.ServiceSecurityContext;
                }
                catch (ObjectDisposedException) { }

                if (sec != null)
                {
                    IdentityName = IdentityHelper.GetUsername(true, IdentityScope.ClaimSet, sec);
                }
            }

            ErrorContext = new ActivationErrorContext();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ActivationContextExtension"/> class.
        /// </summary>
        /// <param name="requestMessage">The request message.</param>
        /// <param name="messageBuffer">The message buffer.</param>
        internal ActivationContextExtension(Message requestMessage, MessageBuffer messageBuffer)
            : this(requestMessage)
        {
            _messageBuffer = messageBuffer;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ActivationContextExtension"/> class.
        /// </summary>
        /// <param name="requestMessage">The request message.</param>
        /// <param name="messageBuffer">The message buffer.</param>
        /// <param name="createBufferForErrors">if set to <c>true</c> a seperate buffer is preserved for error handlers processing.</param>
        internal ActivationContextExtension(Message requestMessage, MessageBuffer messageBuffer, bool createBufferForErrors)
            : this(requestMessage, messageBuffer)
        {
            _createBufferForErrors = createBufferForErrors;
        }
        #endregion

        internal ActivationErrorContext LoadErrorContext()
        {
            if (!_errorContextLoaded)
            {
                ErrorContext.RequestMessage = GetRequestMessage(false);
                ErrorContext.MessageBuffer = _messageBuffer;
                ErrorContext.ServiceType = ServiceType;
                ErrorContext.ServiceName = ServiceName;
                ErrorContext.ServiceNamespace = ServiceNamespace;
                ErrorContext.Action = Action;
                ErrorContext.IdentityName = IdentityName;
                ErrorContext.Parameters = Parameters;

                SetSecurityContext(OperationContext.Current);

                _errorContextLoaded = true;
            }

            return ErrorContext;
        }

        /// <summary>
        /// Gets the request message.
        /// </summary>
        /// <returns></returns>
        public Message GetRequestMessage()
        {
            return GetRequestMessage(false);
        }

        internal Message GetRequestMessage(bool forceDispose)
        {
            if (_messageBuffer != null)
            {
                _requestMessage = _messageBuffer.CreateMessage();
                
                DisposeBuffer(forceDispose);
            }

            return _requestMessage;
        }

        void SetSecurityContext(OperationContext context)
        {
            try
            {
                ErrorContext.SecurityContext = context.ServiceSecurityContext;
            }
            catch (ObjectDisposedException) { }
        }

        internal void SetParameters(object[] parameters)
        {
            ErrorContext.Parameters = parameters;
            Parameters = parameters;
        }

        internal void DisposeBuffer(bool force)
        {
            if ((!_createBufferForErrors || force) && _messageBuffer != null)
            {
                lock (this)
                {
                    if (_messageBuffer != null)
                    {
                        _messageBuffer.Close();
                        _messageBuffer = null;
                    }
                }
            }
        }

        #region IExtension<OperationContext> Members
        void IExtension<OperationContext>.Attach(OperationContext owner)
        {
            SetSecurityContext(owner);
        }

        void IExtension<OperationContext>.Detach(OperationContext owner)
        {
        }
        #endregion

        #region IDisposable Members
        /// <summary>
        /// Releases the sources held by the extension.
        /// </summary>
        public void Dispose()
        {
            DisposeBuffer(false);
        }
        #endregion
    }
}
